{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NTaMBjChz7m5"
   },
   "source": [
    "# Introduction to Opik Agent Optimizers\n",
    "\n",
    "You will need:\n",
    "\n",
    "1. A Comet account, for seeing Opik visualizations (free!) - [comet.com](https://comet.com)\n",
    "2. An OpenAI account, for using an LLM\n",
    "[platform.openai.com/settings/organization/api-keys](https://platform.openai.com/settings/organization/api-keys)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "yM1lU0dBBnJs"
   },
   "source": [
    "## Setup\n",
    "\n",
    "This pip-install takes about a minute."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "2Tx6HwuU1rB4",
    "outputId": "926f8ab8-c425-4535-86d4-350e81881402"
   },
   "outputs": [],
   "source": [
    "%pip install opik-optimizer --quiet"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "F-R7wH1JUsvU"
   },
   "source": [
    "Let's make sure we have the correct version:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 36
    },
    "id": "pVgkLU3v2KD8",
    "outputId": "820b19f4-8c76-4dc6-92d6-6eb5217fa2cd"
   },
   "outputs": [],
   "source": [
    "import opik_optimizer\n",
    "\n",
    "opik_optimizer.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "XewXQJj8UsvU"
   },
   "source": [
    "The version should be 0.7.3 or greater.\n",
    "\n",
    "[Comet](https://www.comet.com/site?from=llm&utm_source=opik) provides a hosted version of the Opik platform, [simply create an account](https://www.comet.com/signup?from=llm&utm_source=opik&utm_medium=colab&utm_content=langchain&utm_campaign=opik) and grab your API Key.\n",
    "\n",
    "> You can also run the Opik platform locally, see the [installation guide](https://www.comet.com/docs/opik/self-host/overview/?from=llm&utm_source=opik) for more information.\n",
    "\n",
    "Enter your Comet API key, followed by \"Y\" to use your own workspace:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "H0DNm-un_0Np",
    "outputId": "5ee13cc7-4b21-42d6-bfe2-81d015b939a0"
   },
   "outputs": [],
   "source": [
    "import opik\n",
    "\n",
    "# Configure Opik\n",
    "opik.configure()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "jN5Z73uCUsvV"
   },
   "source": [
    "For this example, we'll use OpenAI models, so we need to set our OpenAI API key:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "vN72mHQy_7Ou",
    "outputId": "9b9686f7-30c6-4652-ee57-e7e9fec85973"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import getpass\n",
    "\n",
    "if \"OPENAI_API_KEY\" not in os.environ:\n",
    "    os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Enter your OpenAI API key: \")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Mv8bdtecUsvV"
   },
   "source": [
    "To optimize any prompt, we'll need three basic things:\n",
    "\n",
    "1. A starting prompt\n",
    "2. A metric\n",
    "3. A dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zVK_KLEXbyqu"
   },
   "source": [
    "## The Dataset\n",
    "\n",
    "In this experiment, we are going to use the **HotPotQA** dataset. This dataset was designed to be difficult for regular LLMs to handle. This dataset is called a \"**multi-hop**\" dataset because answering the questions involves multiple reasoning steps and multiple tool calls, where the LLM needs to infer relationships, combine information, or draw conclusions based on the combined context.\n",
    "\n",
    "Example:\n",
    "\n",
    "> \"What are the capitals of the states that border California?\"\n",
    "\n",
    "You'd need to find which states border California, and then lookup each state's capital.\n",
    "\n",
    "The dataset has about 113,000 such crowd-sourced questions that are constructed to require the introductory paragraphs of two Wikipedia articles to answer.\n",
    "\n",
    "**NOTE:** The name \"HotPot\" comes from the restaurant where the authors came up with the idea of the dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "4gCj_2M3A37D"
   },
   "outputs": [],
   "source": [
    "import opik_optimizer\n",
    "\n",
    "opik_dataset = opik_optimizer.datasets.hotpot(count=300)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YvqslJnA9RMQ"
   },
   "source": [
    "Let's take a look at some dataset items:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "KGZoscWiBInJ",
    "outputId": "60c95575-80ba-47bd-8a11-5d558d042e56"
   },
   "outputs": [],
   "source": [
    "rows = opik_dataset.get_items()\n",
    "rows[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "QBYSHxrXUsvW"
   },
   "source": [
    "We see that each item has a \"question\" and \"answer\". Some of the answers are short and direct, and some of them are more complicated:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8MQC4TllBKgT",
    "outputId": "2683a40f-2d0a-4103-84a2-b250e75d420d"
   },
   "outputs": [],
   "source": [
    "rows[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "feVF8adydeyH"
   },
   "source": [
    "## Opik Project\n",
    "\n",
    "All LLM traces in Opik are saved in a \"project\". We'll put them all in the following project name:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "gTTZ5apsPgUg"
   },
   "outputs": [],
   "source": [
    "project_name = \"optimize-workshop-2025\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "VEEHnyrcdjVw"
   },
   "source": [
    "## The Metric\n",
    "\n",
    "Choosing a good metric for optimization is tricky. For these examples, we'll pick one that will allow us to show improvement, and also provide a gradient of scores. In general though, this metric isn't the best for optimization runs.\n",
    "\n",
    "We'll use \"Edit Distance\" AKA \"Levenshtein Distance\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "T3BKOXxPOpkI"
   },
   "outputs": [],
   "source": [
    "from opik.evaluation.metrics import LevenshteinRatio\n",
    "\n",
    "metric = LevenshteinRatio(project_name=project_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BF9czV9Ue02R"
   },
   "source": [
    "The metric takes two things: the `output` of the LLM and the `reference` (the truth):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "_hMQuJsSexkD",
    "outputId": "9e71b00b-f9c2-415c-e193-caf608d55f44"
   },
   "outputs": [],
   "source": [
    "metric.score(\"Hello\", \"Hello\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "hNleaL5ZPLaA",
    "outputId": "10d08479-8d88-4787-eb5a-ae0b4c6ad344"
   },
   "outputs": [],
   "source": [
    "metric.score(\"Hello!\", \"Hello\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2eI1S-Q_-SwO"
   },
   "source": [
    "The edit distance between \"Hello!\" and \"Hello\" is 1. Here is how the .91 is computed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "7YEltgab4jPs",
    "outputId": "5a0b66f7-b3d5-4c6b-ff71-9264dd58ecc9"
   },
   "outputs": [],
   "source": [
    "edit_distance = 1 - 1 / (len(\"Hello1\") + len(\"Hello\"))\n",
    "edit_distance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "p3gIizQt47AP"
   },
   "source": [
    "For more information see: [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "JarFSV9jb7jD"
   },
   "source": [
    "## Configuation\n",
    "\n",
    "To create the necessary configurations for using an Opik Agent Optimizer, you'll need two things:\n",
    "\n",
    "1. An initial prompt\n",
    "2. A metric\n",
    "\n",
    "We're going to start with a pretty bad prompt... so we can optimize it!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "xxDUfpvgPct7"
   },
   "outputs": [],
   "source": [
    "from opik_optimizer import ChatPrompt\n",
    "\n",
    "initial_prompt = ChatPrompt(\n",
    "    messages=[\n",
    "        {\"role\": \"system\", \"content\": \"Provide an answer to the question\"},\n",
    "        {\"role\": \"user\", \"content\": \"{question}\"},\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Oia79J_e-vSY"
   },
   "source": [
    "And the metric:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "ET_umnWdPmjm"
   },
   "outputs": [],
   "source": [
    "def levenshtein_ratio(dataset_item, llm_output):\n",
    "    metric = LevenshteinRatio()\n",
    "    return metric.score(reference=dataset_item[\"answer\"], output=llm_output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2aDp9vjw5hIQ"
   },
   "source": [
    "As you can see our metric function is a wrapper around the built-in LevenshteinRatio(). For most Opik metrics, we need two parameters.\n",
    "1. 'output' - which is the output of the LLM\n",
    "2. 'reference' - the correct answer (provided by the database item \"answer\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "VBNAMIglcVIo"
   },
   "source": [
    "## FewShotBayesianOptimizer\n",
    "\n",
    "The FewShotBayesianOptimizer name indicates two things:\n",
    "\n",
    "1. It will produce Chat Prompts, or FewShot examples as described in the slides.\n",
    "2. Secondly, it describes how it searches for the best set of these FewShot examples.\n",
    "\n",
    "To use this optimizer, we import it and create an instance, passing in the project name and model parameters:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "EbaysjG1PRNd"
   },
   "outputs": [],
   "source": [
    "from opik_optimizer import (\n",
    "    FewShotBayesianOptimizer,\n",
    ")\n",
    "\n",
    "optimizer = FewShotBayesianOptimizer(\n",
    "    model=\"openai/gpt-4o-mini\",\n",
    "    model_parameters={\"temperature\": 0.1, \"max_tokens\": 5000},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zuZkkZqGWg4C"
   },
   "source": [
    "### Baseline\n",
    "\n",
    "Before we optimize this prompt (\"Provide an answer to the question\") let's see what the bare prompt does by itself on the dataset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 66,
     "referenced_widgets": [
      "1d70a40ab20746af9e2a379772f6cc30",
      "ccd2f2f9ca864c4987b9ea45ec6f267a",
      "a5196fb7e98a4951bb67a99291767c44",
      "048cacd8a6d142d78a09def88a5fa96e",
      "978d466d1a0e49e18ae0f4e976cf66ab",
      "d85f2850386a48149ff8c38d61c23030",
      "3f4a36fdec104cbd940d75d1630f641c",
      "c23fa590b1df49fab3d68d09c61045ac",
      "c31f4fe47e7c47528a8d3bccc6385787",
      "8d662a238bee4b1cbc58402f85bc2be3",
      "0696c06bfc394a5584f10d027dd96c9a"
     ]
    },
    "id": "fYEhqP2WP0B_",
    "outputId": "5470fbc5-9182-4eb3-f5ca-41d01580fef7"
   },
   "outputs": [],
   "source": [
    "score = optimizer.evaluate_prompt(\n",
    "    prompt=initial_prompt,\n",
    "    dataset=opik_dataset,\n",
    "    metric=levenshtein_ratio,\n",
    "    n_samples=100,\n",
    "    n_threads=4,\n",
    ")\n",
    "score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UtFggdB67tmB"
   },
   "source": [
    "In my run, it scored about 16% correct. [I say \"percent correct\" but because we are using edit distance, that isn't quite accurate. But we can think of it this way.] Your result may be somewhat different but is probably between 10% and 20% correct.\n",
    "\n",
    "Ok, let's optimize that prompt!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000,
     "referenced_widgets": [
      "380b9cf3445b47a693cb95c4a3780935",
      "16164afdc4f64c79865fbc8bc1b3a91c",
      "6649b791c66949169fd98b5cf00d531c",
      "5a4421ffedbd41878c3772b4d5e18771",
      "0447e29be8294d798db1f6f1aed75d4f",
      "2d211f5d4e394af0a22fe384b0157f5b",
      "c6c7a2fc62fd4be695712246f83065d2",
      "9e7cacb4e0fb4f6aa5659c0f7dbe108b"
     ]
    },
    "id": "3Q3ENgSARX2m",
    "outputId": "de00e851-4128-42e2-aa80-77bb36c06aba"
   },
   "outputs": [],
   "source": [
    "result = optimizer.optimize_prompt(\n",
    "    prompt=initial_prompt,\n",
    "    dataset=opik_dataset,\n",
    "    metric=levenshtein_ratio,\n",
    "    n_samples=50,\n",
    "    n_trials=3,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "nJVmhi5Gcnkj",
    "outputId": "28c9147c-5429-4fd1-8a5f-90f2ab47c37c"
   },
   "outputs": [],
   "source": [
    "result.display()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1Zw5UhcCF3lm"
   },
   "source": [
    "Well done optimizer!\n",
    "\n",
    "The percentage correct went from about 15% to about 50% correct."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3_ppCUe6AGH8"
   },
   "source": [
    "What did we find? The result is a series of messages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "o1nDwurG_2qL",
    "outputId": "bd5b9b2f-dcc5-4adb-fcf5-b3046a063bea"
   },
   "outputs": [],
   "source": [
    "result.prompt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "uOa5gfYhUsvb"
   },
   "source": [
    "## Opik Visualization UI\n",
    "\n",
    "When you create an Optimization Run, you'll see it in the Opik UI (either running locally or hosted).\n",
    "\n",
    "If you go to your `comet.com/opik/YOURID/optimizations` page, you'll see your run at the top of the list:\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/comet-ml/opik/refs/heads/main/sdks/opik_optimizer/docs/images/optimizer-ui.png\" width=\"500px\">\n",
    "\n",
    "Along the top of the page you'll see a running history of the metric scores, with the latest dataset selected.\n",
    "\n",
    "If you click on your run, you'll see the set of trials that ran durring this optimization run. Running across the top of this page are the scores just for this trial. On the top right you'll see the best so far:\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/comet-ml/opik/refs/heads/main/sdks/opik_optimizer/docs/images/optimize-trials.png\" width=\"500px\">\n",
    "\n",
    "If you click on a trial, you'll see the prompt for that trial:\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/comet-ml/opik/refs/heads/main/sdks/opik_optimizer/docs/images/optimizer-prompt.png\" width=\"500px\">\n",
    "\n",
    "From the trial page you can also see the Trial items, and even dig down (mouse over the \"Evaluation task\" column on a row) to see the traces."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "J7bVb_Pp9TUb"
   },
   "source": [
    "## Using Optimized Prompts\n",
    "\n",
    "How can we use the optimized results?\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6uWZEjWTFcpJ"
   },
   "source": [
    "Once we have the \"chat_messages\", we can do the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "-wOD7oca9gjs"
   },
   "outputs": [],
   "source": [
    "from litellm.integrations.opik.opik import OpikLogger\n",
    "import litellm\n",
    "\n",
    "opik_logger = OpikLogger()\n",
    "litellm.callbacks = [opik_logger]\n",
    "\n",
    "\n",
    "def query(question, chat_messages):\n",
    "    messages = chat_messages[:-1]  # Cut off the last one\n",
    "    # replace it with question in proper format:\n",
    "    messages.append({\"role\": \"user\", \"content\": '{\"question\": \"%s\"}\"}' % question})\n",
    "\n",
    "    response = litellm.completion(\n",
    "        model=\"gpt-4o-mini\",\n",
    "        temperature=0.1,\n",
    "        max_tokens=5000,\n",
    "        messages=messages,\n",
    "    )\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 36
    },
    "id": "2nu5Tqdl9wa8",
    "outputId": "3b1b05a1-0285-40a8-e548-67c8f135b4b2"
   },
   "outputs": [],
   "source": [
    "query(\"When was David Chalmers born?\", result.details[\"chat_messages\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 36
    },
    "id": "xI3mOXQ4_btf",
    "outputId": "7bbd69ab-fe79-4712-dc77-f6308f7e6532"
   },
   "outputs": [],
   "source": [
    "query(\"What weighs more: a baby elephant or an SUV?\", result.details[\"chat_messages\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2x0JkiYhFsQq"
   },
   "source": [
    "If it says \"elephant\" that is not correct!\n",
    "\n",
    "We'll need to use an agent with tools to get a better answer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "uSDJ1bFx51kd"
   },
   "source": [
    "# Next Steps\n",
    "\n",
    "You can try out other optimizers. More details can be found in the [Opik Agent Optimizer documentation](https://www.comet.com/docs/opik/agent_optimization/overview)."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "py312_llm_eval",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
